/*
 * i2c.c
 *
 *  Created on: Mar 21, 2011
 *      Author: Vishal Verma
 */

/* A program demonstrating application of AT24C512 as an I2C EEPROM. */

#include <at89c51ed2.h>  //also includes 8052.h and 8051.h
#include <mcs51reg.h>
#include <stdio.h>
#include "i2c.h"

void delay(int i)
/* A delay sunroutine */
{
	unsigned int j;
	for ( ; i ; i-- )
		for ( j = 60000; j ; j-- )
			;
}

void delay_millisec(unsigned int delay)
{
	/* A subroutine for generating 1ms delay */

	TMOD |= 0x01; /* timer 0; mode 1 */
	TH0 = 0xF4;
	TL0 = 0x03; /* with 18.423MHz crystal; delay = 0.326*3068
		     * us--approximatly 1ms */

	delay /= 1.665;  //correction for clock
	for (  ; delay != 0; delay-- ) {

		TR0 = SET; /* start timer */

		while(TF0 == CLEAR)
			; /* wait for timer overflow */
		TF0 = CLEAR; /* clear flags */
	}
	TR0 = CLEAR; /* stop timer */
}

/* I2C functions */
void start()
{
	/* I2C START condition */
	SCL = SDA = HIGH;
	NOP;
	SDA = LOW;
	NOP;
	SCL = LOW;
}

void stop()
{
	/* I2C STOP condition */
	SDA = LOW;
	SCL = HIGH;
	NOP;
	SDA = HIGH;
	NOP;
	SCL = LOW;
}

unsigned char clock()
{
	/* A subroutine to generate a clock pulse and return the
	 * status of the data bus during the clock period */
	unsigned char status = 0;
	SCL = HIGH;
	NOP;
	while(!SCL);		/* eliminate ripples */
	status = SDA;
	SCL = LOW;
	return (status);
}

unsigned char write(unsigned char byte)
{
	/* I2C WRITE operation, Write a byte on SDA and return the
	 * acknowledgement */
	unsigned char mask = 0x80;
	unsigned char status= ACK;

	for ( ; mask > 0 ;  ) {
		SDA = (byte & mask) ? ( SET ):( CLEAR ) ;
		mask >>= 1;
		clock();
	}
 	return (clock());	/* return the status of SDA line on the 9th pulse */
}

unsigned char read(unsigned char status)
{
	/* I2C READ operation, read one byte from the transmitter and
	 * give ack/nack to it */
	unsigned char byte = 0;
	unsigned char count, level = 0;
	SDA = HIGH;		/* configure SDA as input */
	for ( count = 0; count < 8; count++ ) {
		byte <<= 1;
		level = clock();
		byte |= level;
	}
	SDA = status;		/* status = 1 :- NACK
				 * status = 0 :- ACK */
	clock();
	return byte;
}

/* EEPROM functions */
unsigned char byte_write( unsigned int addr, unsigned char val )
{
	/*  A write operation requires two 8-bit data word addresses
	 *  following the device address word and acknowledgment. Upon
	 *  receipt of this address, the EEPROM will again respond
	 *  with and then clock in the first 8-bit data
	 *  word. Following receipt of the 8-bit data word, the EEPROM
	 *  will output . The addressing device, such as a
	 *  microcontroller, then must terminate the write sequence
	 *  with a stop condition. At this time the EEPROM enters an
	 *  internally-timed write cycle, tWR, to the nonvolatile
	 *  memory.  All inputs are disabled during this write cycle
	 *  and the EEPROM will not respond until the write is
	 *  complete */

	unsigned char status = ERROR;

	start();

	if ( write(EEPROM_WRITE_ADDR) == ACK ) {
		//if ( write( (addr & 0xFF00) >> 8 ) == ACK ) {
			if ( write( (addr & 0x00FF) ) == ACK ) {
				if ( write(val) == ACK ) {
					status = SUCCESS;
				}
				else
					printf("write val failed!!\n\r");
			}
			else
				printf("write low byte failed!!\n\r");
		//}
		//else
			//printf("write high byte failed!!\n\r");
	}
	else
		printf("write device id failed!!\n\r");


	stop();

	//delay_millisec(10); 		/* Twr = 10ms max */

	return status;
}


unsigned char page_write(unsigned int addr, const unsigned char *str )
{
	/* A page write sequence. A maximum of 128 bits are
	 * permissable beyond which a page over write occurs */

	unsigned char status = ERROR;
	unsigned char bytes;

	start();

	if ( write(EEPROM_WRITE_ADDR) == ACK ) {
		//if ( write( (addr & 0xFF00) >> 8 ) == ACK ) {
			if ( write( (addr & 0x00FF) ) == ACK ) {
				for ( bytes = 0; str[bytes]; bytes++ ) {
					if (bytes <= 25) {
			/* check for page overflow*/
						if (write(str[bytes]) != ACK) {
							printf("page write to EEPROM failed!!\n\r");
							status = ERROR;
							goto STOP;
						}
						printf("Writing %c\n\r", str[bytes]);
					}
					else { /* This byte needs to be
						* written to the next
						* page*/

						stop(); /* stop the
							 * operation  */

						delay_millisec(10);
						//status = page_write( addr+1, str);
						status = SUCCESS;
						/* perform a page
						 * write again */
						return status;
					}
				}
				//write('\0'); // write nul character at the end of the string

				status = SUCCESS;
			}
			else
				printf("write low byte failed!!\n\r");
		//}
		//else
			//printf("write high byte failed!!\n\r");
	}
	else
		printf("write device id failed!!\n\r");


 STOP:
	stop();

	delay_millisec(10);

//	while(SDA);		/* wait till SDA goes LOW */

	return status;
}


unsigned char byte_read( unsigned int addr )
{
	/* A random read requires byte write sequence to load in the
	 * data word address. Once the device address word and data
	 * word address are clocked in and acknowledged by the EEPROM,
	 * the microcontroller must generate another start
	 * condition. The microcontroller now initiates a current
	 * address read by sending a device address with the
	 * Read/Write select bit high. The EEPROM acknowledges the
	 * device address and serially clocks out the data word. The
	 * microcontroller does not respond with but does generate a
	 * following stop condition */

	unsigned char val = ERROR;

	start();
	if ( write(EEPROM_WRITE_ADDR) == ACK ) {
		//if ( write( (addr & 0xFF00) >> 8 ) == ACK ) {
			if ( write( (addr & 0x00FF) ) == ACK ) {
				/* Dummy write successful */
				start();

				if ( write(EEPROM_READ_ADDR) == ACK) {
					val = read(NACK);
					//printf("Reading byte %d\n\r", val);
				}
				else
					printf("write device Read id failed!!!\n\r");
			}
			else
				printf("write addr low byte failed!!!\n\r");

		//}
		//else
			//printf("write addr high byte failed!!!\n\r");
	}
	else
		printf("write device_write id failed!!!\n\r");

	stop();

	return val;
}

/* Current address Read */

unsigned char current_read()
{
	/* read a byte from the last address read/written + 1 */

	unsigned char byte = ERROR;

	start();

	if ( write(EEPROM_READ_ADDR) == ACK ) {
		byte = read(NACK);
	}
	else
		printf("write(read id) failed!! \n\r");

	stop();

	return byte;
}

/* Sequential read */
unsigned int seq_read( unsigned int size, __idata unsigned char *buf, unsigned int addr)
{
	/* read sequentially 'size' bytes to buffer 'buf' starting
	 * from address 'addr' */
	unsigned int num = 0;	/* count the number of bytes read */
	unsigned char byte = 0;

	//buf[num] = byte_read(addr);
	//num++;

	start();
	if ( write(EEPROM_WRITE_ADDR) == ACK ) {
		//if ( write( (addr & 0xFF00) >> 8 ) == ACK ) {
			if ( write( (addr & 0x00FF) ) == ACK ) {
				/* Dummy write successful */
				//printf("Reading (R) %x\n\r", buf[num]);
				start();

				if ( write(EEPROM_READ_ADDR) == ACK ) {
					for (  ; size >= 1 ; size-- ) {
						buf[num] = read(ACK);
						printf("Reading (S) %02d -  %x (%c)\n\r", num, buf[num], buf[num]);
						num++;
					}
					buf[num] = read(NACK); /* for last byte to be read */
					printf("Reading (NACK) %02d -  %x (%c)\n\r", num, buf[num], buf[num]);
					num++;
				}
				else {
					printf("Device not selected!\n\r");
				}
			}
			else {
				printf("write addr low byte failed!!!\n\r");
			}
		//}
		//else {
			//printf("write addr high byte failed!!!\n\r");
		//}
	}
	else {
		printf("write addr high byte failed!!!\n\r");
	}

	stop();
	return (num);
}
